#include "include/CHttpFile.h"
#include "include/CHttpClient.h"
#include "include/CGetHostByName.h"


extern const char * gethostbyname_qg (const char *name, int timeOutMs);

BASENET_BEGIN_NAMESPACE


// 客服端回调函数
static void httpClientCB(struct mg_connection *c, int ev, void *ev_data, void *fn_data)
{
	CHttpFile *p = (CHttpFile *)fn_data;
	if(p) {
		p->__httpClientCB(c,ev,ev_data);
	}
}

// 连接超时
static void httpConTimeOutCB(void *arg)
{
	CHttpFile *p = (CHttpFile *)arg;
	if(p) {
		p->__httpConTimeOutCB();
	}

}

//构造函数
CHttpFile::CHttpFile()
{
	m_lPrev_second = 0;
	m_sMethod = GET;
	m_bRunStatus = false;
	m_iConTime = 3;
	m_iConTimeTmp = 0;
	m_iRecvTime = 5;
	m_sCon = nullptr;
	m_pHeadParams = nullptr;
	m_pParms = nullptr;;
	m_fp = nullptr;
	// 要下载或上传的文件总大小
	m_fileTotalSize = 0;
	// 已下载或已上传的文件总大小
	m_fileCurrentSize = 0;
	m_fileDownOrUpStatus = false;
	m_isDownFile = false;
	m_isHandleBlockData = false;
	m_isAutoFileType = false;
	m_isAutoFileName = false;
	memset(&m_sTlsOpts,0x00,sizeof(struct mg_tls_opts));
	mg_mgr_init(&m_sMgr);

	CGetHostByName::initMongoose();
}

CHttpFile::CHttpFile(unsigned long IoSize)
{
	m_lPrev_second = 0;
	m_sMethod = GET;
	m_bRunStatus = false;
	m_iConTime = 3;
	m_iConTimeTmp = 0;
	m_iRecvTime = 5;
	m_sCon = nullptr;
	m_pHeadParams = nullptr;
	m_pParms = nullptr;;
	m_fp = nullptr;
	// 要下载或上传的文件总大小
	m_fileTotalSize = 0;
	// 已下载或已上传的文件总大小
	m_fileCurrentSize = 0;
	m_fileDownOrUpStatus = false;
	m_isDownFile = false;
	m_isHandleBlockData = false;
	m_isAutoFileType = false;
	m_isAutoFileName = false;
	memset(&m_sTlsOpts,0x00,sizeof(struct mg_tls_opts));
	mg_mgr_init(&m_sMgr);
	m_sMgr.mg_ioSize = IoSize;

	CGetHostByName::initMongoose();
}

// 析构函数
CHttpFile::~CHttpFile()
{
	mg_mgr_free(&m_sMgr);
}

const char *CHttpFile::getHttpMethod()
{
	switch(m_sMethod){
		case GET:
			return "GET";
		case POST:
			return "POST";
	}
	return "GET";
}

static struct mg_str guess_content_type(struct mg_str path, const char *extra) {
  // clang-format off
  struct mimeentry { struct mg_str extension, value; };
  #define MIME_ENTRY(a, b) {{a, sizeof(a) - 1 }, { b, sizeof(b) - 1 }}
  // clang-format on
  const struct mimeentry tab[] = {
      MIME_ENTRY("html", "text/html; charset=utf-8"),
      MIME_ENTRY("htm", "text/html; charset=utf-8"),
      MIME_ENTRY("css", "text/css; charset=utf-8"),
      MIME_ENTRY("js", "text/javascript; charset=utf-8"),
      MIME_ENTRY("gif", "image/gif"),
      MIME_ENTRY("png", "image/png"),
      MIME_ENTRY("jpg", "image/jpeg"),
      MIME_ENTRY("jpeg", "image/jpeg"),
      MIME_ENTRY("woff", "font/woff"),
      MIME_ENTRY("ttf", "font/ttf"),
      MIME_ENTRY("svg", "image/svg+xml"),
      MIME_ENTRY("txt", "text/plain; charset=utf-8"),
      MIME_ENTRY("avi", "video/x-msvideo"),
      MIME_ENTRY("csv", "text/csv"),
      MIME_ENTRY("doc", "application/msword"),
      MIME_ENTRY("exe", "application/octet-stream"),
      MIME_ENTRY("gz", "application/gzip"),
      MIME_ENTRY("ico", "image/x-icon"),
      MIME_ENTRY("json", "application/json"),
      MIME_ENTRY("mov", "video/quicktime"),
      MIME_ENTRY("mp3", "audio/mpeg"),
      MIME_ENTRY("mp4", "video/mp4"),
      MIME_ENTRY("mpeg", "video/mpeg"),
      MIME_ENTRY("pdf", "application/pdf"),
      MIME_ENTRY("shtml", "text/html; charset=utf-8"),
      MIME_ENTRY("tgz", "application/tar-gz"),
      MIME_ENTRY("wav", "audio/wav"),
      MIME_ENTRY("webp", "image/webp"),
      MIME_ENTRY("zip", "application/zip"),
      MIME_ENTRY("3gp", "video/3gpp"),
      {{0, 0}, {0, 0}},
  };
  size_t i = 0;
  struct mg_str k, v, s = mg_str(extra);

  // Shrink path to its extension only
  while (i < path.len && path.ptr[path.len - i - 1] != '.') i++;
  path.ptr += path.len - i;
  path.len = i;

  // Process user-provided mime type overrides, if any
  while (mg_commalist(&s, &k, &v)) {
    if (mg_strcmp(path, k) == 0) return v;
  }

  // Process built-in mime types
  for (i = 0; tab[i].extension.ptr != NULL; i++) {
    if (mg_strcmp(path, tab[i].extension) == 0) return tab[i].value;
  }

  return mg_str("text/plain; charset=utf-8");
}

std::string CHttpFile::getContentFileType(const std::string& contentType)
{
    std::map<std::string, std::string> fileTypes = {
        {"text/html; charset=utf-8", "html"},
        {"text/css; charset=utf-8", "css"},
        {"text/javascript; charset=utf-8", "js"},
        {"image/gif", "gif"},
        {"image/png", "png"},
        {"image/jpeg", "jpg"},
        {"font/woff", "woff"},
        {"font/ttf", "ttf"},
        {"image/svg+xml", "svg"},
        {"text/plain; charset=utf-8", "txt"},
        {"video/x-msvideo", "avi"},
        {"text/csv", "csv"},
        {"application/msword", "doc"},
        {"application/octet-stream", "exe"},
        {"application/gzip", "gz"},
        {"image/x-icon", "ico"},
        {"application/json", "json"},
        {"video/quicktime", "mov"},
        {"audio/mpeg", "mp3"},
        {"video/mp4", "mp4"},
        {"video/mpeg", "mpeg"},
        {"application/pdf", "pdf"},
        {"application/tar-gz", "tgz"},
        {"audio/wav", "wav"},
        {"image/webp", "webp"},
        {"application/zip", "zip"},
        {"video/3gpp", "3gp"},
    };

    auto fileType = fileTypes.find(contentType);
    if (fileType != fileTypes.end()) {
        return fileType->second;
    }

    return "";
}


// 发送请求数据
void CHttpFile::sendReuireData(struct mg_connection *c, int ev, void *ev_data)
{
	CMultipartParser parser;
	string extra_headers="Pragma: no-cache\r\nCache-Control: no-cache\r\n";
	bool isSetMultipart = false;
	// post提交参数对只能使用multipart/form-data提交
	if(m_sMethod == POST && ((m_pParms && m_pParms->size()!=0) || !m_sFileName.empty()))
	{
		isSetMultipart = true;
		extra_headers +="Content-Type: multipart/form-data; boundary="+parser.boundary()+"\r\n";
	}


	// 头组装
	if(m_pHeadParams) {
		for (MAP_PARAMS::iterator itr = m_pHeadParams->begin(); itr!=  m_pHeadParams->end(); ++itr) {
			extra_headers += itr->first + ":" + itr->second + "\r\n";
		}
	}
	if(!m_isDownFile && !isSetMultipart){
		// 上传文件需要设置
		extra_headers +="Content-Type: multipart/form-data; boundary="+parser.boundary()+"\r\n";
	}

	// 请求路径
	string path = mg_url_uri(m_sUrl.c_str());

	// 主机名字
	struct mg_str host = mg_url_host(m_sUrl.c_str());
	unsigned short port = mg_url_port(m_sUrl.c_str());
	if(m_isDownFile){
		// 下载文件
		if(port > 0 && port != 80 && port != 443 && port != 1883 && port != 8883){
			mg_printf(c,"%s %s HTTP/1.1\r\n"
						"Host: %.*s:%d\r\n"
						"Content-Length:%d\r\n"
						"%s\r\n",
						getHttpMethod(),path.c_str(),
						host.len,host.ptr,port,
						0,
						extra_headers.c_str()
					    );
	#if 0
            qDebug("--- %s %s HTTP/1.1\r\n"
							"Host: %.*s:%d\r\n"
							"Content-Length:%d\r\n"
							"%s\r\n",
							getHttpMethod(),path.c_str(),
							host.len,host.ptr,port,
							0,
							extra_headers.c_str()
						    );
	#endif
			return;
		}
		mg_printf(c,"%s %s HTTP/1.1\r\n"
					"Host: %.*s\r\n"
					"Content-Length:%d\r\n"
					"%s\r\n",
					getHttpMethod(),path.c_str(),
					host.len,host.ptr,
					0,
					extra_headers.c_str()
				    );
	#if 0
        qDebug("--- %s %s HTTP/1.1\r\n"
						"Host: %.*s\r\n"
						"Content-Length:%d\r\n"
						"%s\r\n",
						getHttpMethod(),path.c_str(),
						host.len,host.ptr,
						0,
						extra_headers.c_str()
					    );
	#endif
		return;
	}
	// 上传文件
	if(!m_fp)
		m_fp = fopen(m_sFilePath.c_str(), "rb");
	if(!m_fp){
		m_bRunStatus = false;
		return;
	}

    fseek(m_fp, 0, SEEK_END);
    m_fileTotalSize = (long long) ftell(m_fp);
    rewind(m_fp);
	m_fileCurrentSize = 0;
	string body_start;
	string body_end;
	string filename;
	string content_type;
	struct mg_str mime = guess_content_type(mg_str(m_sFilePath.c_str()), NULL);

	size_t last_spliter = m_sFilePath.find_last_of("/\\");
	filename = m_sFilePath.substr(last_spliter + 1);

	body_start += "\r\n--";
	body_start += parser.boundary();
	body_start += "\r\nContent-Disposition: form-data; name=\"";
	body_start += m_sFileName;
	body_start += "\"; filename=\"";
	body_start += filename;
	body_start += "\"\r\nContent-Type: ";
	body_start += string(mime.ptr, mime.len);
	body_start += "\r\n\r\n";

	body_end = "\r\n--";
	body_end += parser.boundary();
	body_end += "--\r\n";

	long long Content_Length = m_fileTotalSize;
	Content_Length += body_start.size();
	Content_Length += body_end.size();

	if(port > 0 && port != 80 && port != 443 && port != 1883 && port != 8883){
		mg_printf(c,"%s %s HTTP/1.1\r\n"
					"Host: %.*s:%d\r\n"
					"Content-Length:%lld\r\n"
					"%s\r\n"
					"%s",
					getHttpMethod(),path.c_str(),
					host.len,host.ptr,port,
					Content_Length,
					extra_headers.c_str(),
					body_start.c_str()
				    );
#if 0
        qDebug("%s %s HTTP/1.1\r\n"
					"Host: %.*s:%d\r\n"
					"Content-Length:%lld\r\n"
					"%s\r\n"
					"%s",
					getHttpMethod(),path.c_str(),
					host.len,host.ptr,port,
					Content_Length,
					extra_headers.c_str(),
					body_start.c_str()
				    );
#endif
	} else {

		mg_printf(c,"%s %s HTTP/1.1\r\n"
					"Host: %.*s\r\n"
					"Content-Length:%lld\r\n"
					"%s\r\n"
					"%s",
					getHttpMethod(),path.c_str(),
					host.len,host.ptr,
					Content_Length,
					extra_headers.c_str(),
					body_start.c_str()
				    );
#if 0
        qDebug("%s %s HTTP/1.1\r\n"
					"Host: %.*s\r\n"
					"Content-Length:%lld\r\n"
					"%s\r\n"
					"%s",
					getHttpMethod(),path.c_str(),
					host.len,host.ptr,
					Content_Length,
					extra_headers.c_str(),
					body_start.c_str()
				    );
#endif
	}
	char *buf = new char[1024*1024*1];
	int ret = 0;
	while(true){
		ret = fread(buf, 1, 1024*1024*1, m_fp);
		mg_send(c, buf, ret);
		m_fileCurrentSize += ret;
		if(ret < 1024*1024*1){
			break;
		}
		if(m_fileCurrentSize >= m_fileTotalSize){
			break;
		}
	}
	mg_send(c,body_end.c_str(), body_end.size());

	delete[] buf;
	buf = nullptr;
    fclose(m_fp);
    m_fp = nullptr;

}


// 接受http返回数据
void CHttpFile::recvReuireData(struct mg_connection *c, int ev, void *ev_data)
{
	struct mg_http_message *hm = (struct mg_http_message *) ev_data;
//	qDebug("%s", hm->message.ptr);
	m_retBody = string(hm->body.ptr,hm->body.len);
	if(!m_isHandleBlockData){
		// 没有经过块数据处理，直接到结果了，先处理一下
		recvReuireChunkData(c, ev, ev_data, false);
	}
	if(m_fileCurrentSize >= m_fileTotalSize && m_fileTotalSize > 0 )
		m_fileDownOrUpStatus = true;
}

// 接受块数据
void CHttpFile::recvReuireChunkData(struct mg_connection *c, int ev, void *ev_data, bool isCHunk)
{
	if(!m_isDownFile){
		// 不是下载文件不用处理快数据
		return;
	}
	m_isHandleBlockData = true;
	struct mg_http_message *hm = (struct mg_http_message *) ev_data;
	struct mg_str data_mg = hm->chunk;
	if(!isCHunk){
		data_mg = hm->body;
	}
	if(data_mg.len <= 0){
		if(m_fp){
            fflush(m_fp);
			fclose(m_fp);
			m_fp = nullptr;
			m_fileDownOrUpStatus = true;
		}
		m_bRunStatus = false;
		return;
	}
	if(m_sMethod == GET && !m_sFilePath.empty() && atoi(hm->uri.ptr) == 200)
	{
		if(m_fileTotalSize == 0){
			for(int i = 0; i < MG_MAX_HTTP_HEADERS; i++){
				if(string(hm->headers[i].name.ptr, hm->headers[i].name.len) == "Content-Length"){
					// 要下载或上传的文件总大小
					m_fileTotalSize = atoll(string(hm->headers[i].value.ptr, hm->headers[i].value.len).c_str());
					break;
				}
			}
		}
		if(!m_fp){
			// 自动设置文件名
			if(m_isAutoFileName){
				m_sFilePath += CUtils::getLongOnlyId();
			}
			// 自动获取文件类型
			if(m_isAutoFileType){
				string type;
				for(int i = 0; i < MG_MAX_HTTP_HEADERS; i++){
					if(string(hm->headers[i].name.ptr, hm->headers[i].name.len) == "Content-Length"){
						// 要下载或上传的文件总大小
						type = string(hm->headers[i].value.ptr, hm->headers[i].value.len);
						break;
					}
				}
				type = getContentFileType(type);
				m_sFilePath += "." + type;
			}
			m_fp = fopen(m_sFilePath.c_str(), "wb");
		}
		if(!m_fp){
			return;
		}
		// 已下载或已上传的文件总大小
		m_fileCurrentSize += data_mg.len;
//		qDebug("%lld:%lld", m_fileTotalSize, m_fileCurrentSize);
		fwrite(data_mg.ptr, 1, data_mg.len, m_fp);
		if(isCHunk)
		mg_http_delete_chunk(c, hm);

		if(m_fileTotalSize > 0 && m_fileCurrentSize >= m_fileTotalSize){
			if(m_fp){
                fflush(m_fp);
				fclose(m_fp);
				m_fp = nullptr;
				m_fileDownOrUpStatus = true;
			}
			m_bRunStatus = false;
		}
	}
}

// 清除http请求
void CHttpFile::cancelReuire()
{
	m_bRunStatus = false;
}

// 请求回调
void CHttpFile::__httpClientCB(struct mg_connection *c, int ev, void *ev_data)
{
//	qDebug("%d", ev);
	if (ev == MG_EV_CONNECT) {
		m_sCon = c;
		m_lPrev_second = 0;
		c->label[0] = 'X';
		if (mg_url_is_ssl(m_sUrl.c_str())) {
			 struct mg_str host = mg_url_host(m_sUrl.c_str());
			 if(m_sTlsOpts.srvname.len == 0){
				 m_sTlsOpts.srvname = host;
			 }
			 mg_tls_init(c, &m_sTlsOpts);
		 }
		sendReuireData(c,ev,ev_data);
    } else if (ev == MG_EV_HTTP_MSG) {
    	recvReuireData(c,ev,ev_data);
	    c->is_closing = 1;
	    m_bRunStatus = false;
	}else if (ev == MG_EV_HTTP_CHUNK) {
    	recvReuireChunkData(c,ev,ev_data);
	} else if (ev == MG_EV_ERROR) {
        qDebug("MG_EV_ERROR:%s",ev_data);
		m_sCon = NULL;
	    if(m_iConTimeTmp >= m_iConTime){
	    	m_bRunStatus = false;
	    }
    } else if (ev == MG_EV_POLL && c->label[0] == 'X') {
    	unsigned long second = (*(unsigned long *) ev_data) / 1000;
    	if(m_lPrev_second == 0) {
    		m_lPrev_second = second;
    	}
    	if(abs((long)(second - m_lPrev_second)) >= m_iRecvTime){
			c->is_closing = 1;
			m_bRunStatus = false;
            qDebug("m_iRecvTime");
    	}

    } else if(ev == MG_EV_READ) {
    	m_lPrev_second = 0;
    } else if(ev == MG_EV_CLOSE) {
    	m_bRunStatus = false;
    	m_sCon= NULL;
    } else if(ev == MG_EV_OPEN) {

    }

}


// 定时器
void CHttpFile::__httpConTimeOutCB()
{
	if(m_iConTimeTmp < m_iConTime){
		// 连接超时
		m_iConTimeTmp += 3;
		if(m_sCon == NULL){
			m_sCon = mg_http_connect(&m_sMgr, m_sUrl.c_str(), httpClientCB, this);
		}
	}else {
		if(m_sCon == NULL){
			m_bRunStatus = false;
		}
	}
}

// 执行请求
void CHttpFile::requireRun() {

	struct mg_timer time;;
	int topts = MG_TIMER_REPEAT | MG_TIMER_RUN_NOW;
	mg_timer_init(&m_sMgr,&time, 3000, topts, httpConTimeOutCB, this);
	m_bRunStatus = true;
	while(m_bRunStatus){
		mg_mgr_poll(&m_sMgr, 1000);
	}
	mg_timer_free(&m_sMgr,&time);
}

bool CHttpFile::startReuire()
{
	requireRun();
	return m_fileDownOrUpStatus;
}


// 初始化连接参数
void CHttpFile::initContParm(int method,string url,int conTimeout,int recvTimeout)
{
	m_sMethod = method;
	m_sUrl = url;
	m_iConTime = conTimeout;
	m_iRecvTime = recvTimeout;
}

// 初始化数据参数
void CHttpFile::initDataParm(MAP_PARAMS *parm,MAP_PARAMS *extra_headers,string fileName,string filePath, bool isAutoFileName, bool isAutoFileType)
{
	// 请求头
	m_pHeadParams = extra_headers;
	m_pParms = parm;
	m_sFileName = fileName;
	m_sFilePath = filePath;

	m_isAutoFileType = isAutoFileType;
	m_isAutoFileName = isAutoFileName;
}

// 初始化tls
void CHttpFile::initTls(struct mg_tls_opts *sTlsOpts)
{
	if(sTlsOpts){
		memcpy(&m_sTlsOpts,sTlsOpts,sizeof(struct mg_tls_opts));
	}
}

// 下载文件
int CHttpFile::downFile(string url,string savePath, int conTimeout,int recvTimeout )
{
	CHttpFile clinet;
	CUtils::pathInspect(savePath);
	clinet.setCurrentDownFile(true);
	clinet.initContParm(GET,url, conTimeout,recvTimeout);
	clinet.initDataParm(nullptr,nullptr,"",savePath);
	bool ret = clinet.startReuire();
	if(ret){
		// 下载成功
		return CODE_DOWN_FILE_SUCCESS;
	}
	string error = clinet.getRetBody();
	if(error.empty()){
		// 下载文件失败，没网
		return CODE_DOWN_FILE_FAIL_NET;
	} else if(error.find("SignatureDoesNotMatch") != error.npos){
		// 下载文件失败，网址无法访问
		return CODE_DOWN_FILE_FAIL_URL;
	} else{
		// 下载文件失败，未知错误
		return CODE_DOWN_FILE_FAIL_UNKNOWN;
	}
}

// 下载文件
int CHttpFile::downFile(unsigned long IoSize, const string &url,const string savePath,int conTimeout,int recvTimeout)
{
	CHttpFile clinet(IoSize);
	CUtils::pathInspect(savePath);
	clinet.setCurrentDownFile(true);
	clinet.initContParm(GET,url, conTimeout,recvTimeout);
	clinet.initDataParm(nullptr,nullptr,"",savePath);
	bool ret = clinet.startReuire();
	if(ret){
		// 下载成功
		return CODE_DOWN_FILE_SUCCESS;
	}
	string error = clinet.getRetBody();
	if(error.empty()){
		// 下载文件失败，没网
        qDebug("DOWN ERROR:%d", CODE_DOWN_FILE_FAIL_NET);
		return CODE_DOWN_FILE_FAIL_NET;
	} else if(error.find("SignatureDoesNotMatch") != error.npos){
		// 下载文件失败，网址无法访问
        qDebug("DOWN ERROR:%d", CODE_DOWN_FILE_FAIL_URL);
		return CODE_DOWN_FILE_FAIL_URL;
	} else{
		// 下载文件失败，未知错误
        qDebug("DOWN ERROR:%d", CODE_DOWN_FILE_FAIL_UNKNOWN);
		return CODE_DOWN_FILE_FAIL_UNKNOWN;
	}
}


string CHttpFile::downFileToDir(unsigned long IoSize, const string &url,const string saveDir, bool isautoFileType, int conTimeout,int recvTimeout)
{
	CHttpFile clinet(IoSize);
	CUtils::pathInspect(saveDir);
	clinet.setCurrentDownFile(true);
	clinet.initContParm(GET,url, conTimeout,recvTimeout);
	clinet.initDataParm(nullptr,nullptr,"",saveDir, true, isautoFileType);
	bool ret = clinet.startReuire();
	if(ret){
		// 下载成功
		return clinet.getDownFilePath();
	}
	string error = clinet.getRetBody();
    qDebug(error.c_str());
	if(error.empty()){
		// 下载文件失败，没网
		return "";
	} else if(error.find("SignatureDoesNotMatch") != error.npos){
		// 下载文件失败，网址无法访问
		return "";
	} else{
		// 下载文件失败，未知错误
		return "";
	}
}

// 上传文件
string CHttpFile::pullFile(int method,const char *url,MAP_PARAMS *headParams,string fileName,string filePath,int conTimeout,int recvTimeout)
{
	CHttpFile clinet;
	clinet.setCurrentDownFile(false);
	clinet.initContParm(method,url,conTimeout,recvTimeout);
	clinet.initDataParm(nullptr,headParams,fileName,filePath);
	clinet.startReuire();
	return clinet.getRetBody();
}


BASENET_END_NAMESPACE
